use core::fmt;

use cosmwasm_std::{
    to_binary, Binary, Querier, QueryRequest, ReadonlyStorage, StdError, Uint128, WasmQuery,
};
use cosmwasm_std::{HumanAddr, StdResult, Storage};
use cosmwasm_storage::Singleton;
use cosmwasm_storage::{Bucket, ReadonlyBucket, ReadonlySingleton};
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};

pub const CONFIG_KEY: &[u8] = b"config";
pub const KEY_TX_BACKLIST: &[u8] = b"tx-backlist";

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ConfigInfo {
    pub owner: HumanAddr,
    pub farm: HumanAddr,
    pub farm_hashcode: String,
    pub reward_contract: HumanAddr,
    pub reward_hashcode: String,
    pub stake_contract: HumanAddr,
    pub stake_hashcode: String,
    pub reward_limit: Uint128,
}

pub fn store_config<S: Storage>(storage: &mut S, data: &ConfigInfo) -> StdResult<()> {
    Singleton::new(storage, CONFIG_KEY).save(data)
}

pub fn read_config<S: Storage>(storage: &S) -> StdResult<ConfigInfo> {
    ReadonlySingleton::new(storage, CONFIG_KEY).load()
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct Staker {
    pub address: HumanAddr,
    pub pool_id: u8,
    pub stake_amount: Uint128,
    pub reward: Uint128,
    pub reward_dept: Uint128,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
    Staker {
        address: HumanAddr,
        block_time: u64,
        pool_id: u8,
    },
}

impl fmt::Display for QueryMsg {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            QueryMsg::Staker { .. } => write!(f, "Staker"),
        }
    }
}

impl QueryMsg {
    /// Returns a StdResult<T>, where T is the "Response" type that wraps the query answer
    ///
    /// # Arguments
    ///
    /// * `querier` - a reference to the Querier dependency of the querying contract
    /// * `block_size` - pad the message to blocks of this size
    /// * `callback_code_hash` - String holding the code hash of the contract being queried
    /// * `contract_addr` - address of the contract being queried
    pub fn query<Q: Querier, T: DeserializeOwned>(
        &self,
        querier: &Q,
        mut block_size: usize,
        callback_code_hash: String,
        contract_addr: HumanAddr,
    ) -> StdResult<T> {
        // can not have block size of 0
        if block_size == 0 {
            block_size = 1;
        }
        let mut msg = to_binary(self)?;
        space_pad(&mut msg.0, block_size);
        querier
            .query(&QueryRequest::Wasm(WasmQuery::Smart {
                callback_code_hash,
                msg,
                contract_addr,
            }))
            .map_err(|err| {
                StdError::generic_err(format!("Error performing {} query: {}", self, err))
            })
    }
}

/// Take a Vec<u8> and pad it up to a multiple of `block_size`, using spaces at the end.
pub fn space_pad(message: &mut Vec<u8>, block_size: usize) -> &mut Vec<u8> {
    let len = message.len();
    let surplus = len % block_size;
    if surplus == 0 {
        return message;
    }

    let missing = block_size - surplus;
    message.reserve(missing);
    message.extend(std::iter::repeat(b' ').take(missing));
    message
}

pub fn store_banklist<S: Storage>(storage: &mut S, user: &HumanAddr) -> StdResult<()> {
    let binding = user.to_string();
    let key = binding.as_bytes();
    let mut banklist_bucket: Bucket<S, bool> = Bucket::new(KEY_TX_BACKLIST, storage);
    banklist_bucket.save(key, &true)?;

    Ok(())
}

pub fn remove_banklist<S: Storage>(storage: &mut S, user: &HumanAddr) -> StdResult<()> {
    let binding = user.to_string();
    let key = binding.as_bytes();
    let mut banklist_bucket: Bucket<S, bool> = Bucket::new(KEY_TX_BACKLIST, storage);
    banklist_bucket.remove(key);

    Ok(())
}

pub fn read_bankuser_by_address<S: ReadonlyStorage>(
    storage: &S,
    user: &HumanAddr,
) -> StdResult<bool> {
    let binding = user.to_string();
    let key = &binding.as_bytes();
    let banklist_bucket: ReadonlyBucket<S, bool> = ReadonlyBucket::new(KEY_TX_BACKLIST, storage);

    match banklist_bucket.load(key) {
        Ok(v) => Ok(v),
        Err(_e) => Err(StdError::generic_err("user not in banklist")),
    }
}
